﻿using Red.Core;
using Red.Toolkit.EngineLogsViewer.Data.Types;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Windows;

namespace Red.Toolkit.EngineLogsViewer.Data
{
    public class EngineLogsViewerViewModel : ViewModel
    {
        #region Data

        private bool _isConnected;

        private string _selectedCategory;
        private string _selectedThreadText;
        private string _textPattern;
        private SeverityLevel _selectedSeverity;

        private List<EngineLogGroupEntry> _activeGroups = new List<EngineLogGroupEntry>();
        private List<uint> _threadIdsGroupToRemove = new List<uint>();

        #endregion

        #region Properties

        public bool IsConnected
        {
            get { return _isConnected; }
            set { _isConnected = value; OnPropertyChanged(); }
        }

        public ObservableCollection<string> Categories { get; private set; }
        public ObservableCollection<string> ThreadIds { get; private set; }
        public ObservableCollection<string> Severities { get; private set; }
        public ObservableCollection<EngineLogAbstractEntry> LogEntries { get; private set; }

        #endregion

        #region Constructor

        public EngineLogsViewerViewModel()
        {
            IsConnected = false;

            Categories = new ObservableCollection<string>();
            ThreadIds = new ObservableCollection<string>();
            Severities = new ObservableCollection<string>();
            LogEntries = new ObservableCollection<EngineLogAbstractEntry>();

            SetDefaultValues();
        }

        #endregion

        #region Public Methods

        public void ClearLogs()
        {
            Categories.Clear();
            ThreadIds.Clear();
            Severities.Clear();
            LogEntries.Clear();

            SetDefaultValues();
        }

        public void SetLogEntries(List<EngineLogAbstractEntry> newEntries)
        {
            ClearLogs();

            LogEntries = new ObservableCollection<EngineLogAbstractEntry>(newEntries);

            // TODO
        }

        #endregion

        #region Private Methods

        private void SetDefaultValues()
        {
            Categories.Add("All");
            ThreadIds.Add("All");

            var severityValues = Enum.GetValues(typeof(SeverityLevel));
            for (int i = 0; i < severityValues.Length; ++i)
            {
                Severities.Add(severityValues.GetValue(i).ToString());
            }
        }

        private void AddThreadIdToCollection(string newThreadIdText)
        {
            bool found = false;
            foreach (var threadId in ThreadIds)
            {
                if (threadId == newThreadIdText)
                {
                    found = true;
                    break;
                }
            }

            if (found == false)
            {
                Application.Current.Dispatcher.Invoke(delegate
                {
                    ThreadIds.Add(newThreadIdText);
                });
            }
        }

        private void AddCategoryToCollection(string newCategory)
        {
            if (string.IsNullOrEmpty(newCategory) == true)
                return;

            bool found = false;
            foreach (var category in Categories)
            {
                if (category == newCategory)
                {
                    found = true;
                    break;
                }
            }

            if (found == false)
            {
                Application.Current.Dispatcher.Invoke(delegate
                {
                    Categories.Add(newCategory);
                });
            }
        }

        private EngineLogAbstractEntry ParseLogText(string logText, DateTime dateTime, uint threadId, uint severityLevel)
        {
            // call order is crucial!
            var severity = LogEntryParser.ParseSeverityLevel(severityLevel);
            var category = LogEntryParser.ExtractCategory(ref logText);
            var text = LogEntryParser.ExtractRawLogText(logText);

            EngineLogAbstractEntry logEntry = null;
            if (string.IsNullOrEmpty(category) == false && category[0] == '#')
            {
                var controlCode = LogEntryParser.ExtractGroupControlCodeFromCategory(ref category);

                switch (controlCode)
                {
                    case EngineLogGroupControlCode.CreateGroup:
                        logEntry = new EngineLogGroupEntry();
                        logEntry.Text = category;
                        logEntry.ThreadId = threadId;
                        logEntry.Timestamp = dateTime;
                        Application.Current.Dispatcher.Invoke(delegate
                        {
                            _activeGroups.Add(logEntry as EngineLogGroupEntry);
                            LogEntries.Add(logEntry);
                        });
                        // TODO
                        break;
                    case EngineLogGroupControlCode.EndGroup:
                        _threadIdsGroupToRemove.Add(threadId);
                        // TODO
                        break;
                }
            }

            // create new log entry
            logEntry = new EngineLogSingleEntry();

            // update collection
            AddThreadIdToCollection(threadId.ToString());
            AddCategoryToCollection(category);

            // set common data
            logEntry.ThreadId = threadId;
            logEntry.Timestamp = dateTime;
            logEntry.Severity = severity;
            logEntry.Category = category;
            logEntry.Text = text;

            // set visibility filters
            logEntry.SetThreadIdFilter(_selectedThreadText);
            logEntry.SetCategoryFilter(_selectedCategory);
            logEntry.SetSeverityFilter(_selectedSeverity);
            logEntry.SetTextFilter(_textPattern);

            return logEntry;
        }

        internal void AddNewLog(DateTime dateTime, uint threadId, uint severity, string logText)
        {
            var newLogEntry = ParseLogText(logText, dateTime, threadId, severity);

            Application.Current.Dispatcher.Invoke(delegate
            {
                if (newLogEntry is EngineLogSingleEntry)
                {
                    bool found = false;
                    for (int i = 0; i < _activeGroups.Count; ++i)
                    {
                        if (newLogEntry.ThreadId == _activeGroups[i].ThreadId)
                        {
                            _activeGroups[i].InternalLogEntries.Add(newLogEntry);
                            found = true;
                            break;
                        }
                    }

                    if (found == false)
                        LogEntries.Add(newLogEntry);
                }

                // remove active group
                for (int i = 0; i < _threadIdsGroupToRemove.Count; ++i)
                {
                    for (int j = 0; j < _activeGroups.Count; ++j)
                    {
                        if (_activeGroups[i].ThreadId == _threadIdsGroupToRemove[j])
                        {
                            _activeGroups.RemoveAt(i);
                            break;
                        }
                    }
                }
                _threadIdsGroupToRemove.Clear();
            });
        }

        #endregion

        #region Filters Metchods

        internal void SearchTextChanged(string textPattern)
        {
            _textPattern = textPattern;

            for (int i = 0; i < LogEntries.Count; ++i)
            {
                LogEntries[i].SetTextFilter(textPattern);
            }
        }

        internal string ConvertToPlainText()
        {
            var sb = new StringBuilder();
            for (int i = 0; i < LogEntries.Count; ++i)
            {
                sb.AppendLine(LogEntries[i].ToString());
            }
            return sb.ToString();
        }

        internal void SelectedSeverityChanged(string selectedSeverity)
        {
            _selectedSeverity = LogEntryParser.ParseSeverityLevel(selectedSeverity);

            for (int i = 0; i < LogEntries.Count; ++i)
            {
                LogEntries[i].SetSeverityFilter(_selectedSeverity);
            }
        }

        internal void SelectedCategoryChanged(string selectedCategory)
        {
            _selectedCategory = selectedCategory;

            for (int i = 0; i < LogEntries.Count; ++i)
            {
                LogEntries[i].SetCategoryFilter(selectedCategory);
            }
        }

        internal void SelectedThreadChanged(string selectedThreadText)
        {
            _selectedThreadText = selectedThreadText;

            for (int i = 0; i < LogEntries.Count; ++i)
            {
                LogEntries[i].SetThreadIdFilter(selectedThreadText);
            }
        }

        #endregion
    }
}
